#!/usr/bin/python
#***************************************************************************
# Copyright notice for this file:
#  Copyright (C) 2012 TheAxeMan
#  _______ _                               _    _
# |__   __| |     ___     /\          ___ | \  / |
#    | |  | |__  / _ \   /  \  __  _ / _ \|  \/  | __ _ _ __
#    | |  | '_ \|  __/  / /\ \ \ \/ |  __/| .  . |/ _` | '_ \
#    | |  | | | | (__/\/ ____ \ )  (| (__/| |\/| | (_| | | | |
#    |_|  |_| |_|\____/_/    \_._/\_.\____|_|  |_|\__,_|_| |_|
#
# Final Fantasy TAS scripts by TheAxeMan
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#***************************************************************************

import sys
import re
import string

from gamedata import GameData
from gamestate import GameState
from rundata import RunSegment
from rundata import RunSegments
from resultdata import ResultSegment
from resultdata import ResultSegments

"""
A class for calculating cost functions. Maintains some internal
data necessary to calculate the cost. 
"""
class CostFunction:

  def __init__(self, gdata):
    self.gdata = gdata
    #use these as default costs
    #self.runCost = 360         #for runnable fights, assuming easy run
    self.runCost = 390         #for runnable fights, adding 30 frames of wait (about average)
    self.fightCost = 1200      #for non-runnable fights
    self.powerCycleCost = 200  #for power cycles

    #some special items to handle shadows...
    self.shadow_id = self.gdata.enemy_group_ids["Ab"]
    #implement a special check for zombie ds after Phantom
    self.zombied_id = self.gdata.enemy_group_ids["4Bb"]
    self.tofr_3f_list = []
    self.tofr_3f_list.append(self.gdata.enemy_group_ids["4Db"])
    self.tofr_3f_list.append(self.gdata.enemy_group_ids["59b"])
    self.tofr_3f_list.append(self.gdata.enemy_group_ids["2Fb"])
    self.zombieDAfterPhantomCost = 650

    self.shadowCost = 450
    #self.shadowCost = 390
    self.consecutiveShadowCost = 400  #extra cost for two shadow fights in a row

    #set up step cost for each area
    self.stepCost = []
    #16 frames a step by default
    for area_id in self.gdata.areas:
      self.stepCost.append(16)
    #but only 8 frames a step in the ocean
    self.stepCost[self.gdata.area_ids["ocean"]] = 8
    #consider making cost for some areas 0 to model changing path cost

    #set up cost for each encounter type
    #first default, assuming that we will just run
    self.battleCost = []
    for enemy_group_id in self.gdata.enemy_groups:
      self.battleCost.append(self.runCost)
    #next, all non-runnable battles get some higher default
    for enemy_group in self.gdata.noRunList:
      self.setBattleCost(enemy_group, self.fightCost)
    #finally, special values for some groups
    #shadows
    self.setBattleCost("Aa", self.shadowCost)
    self.setBattleCost("Ab", self.shadowCost)
    #2-4 wizards not soooo bad
    self.setBattleCost("1Ca", 1200)
    #3-7 wizards
    self.setBattleCost("1Cb", 2000)
    #2 ghosts
    self.setBattleCost("46b", 2300)
    #WzSahag+R.Sahags - always 9, so not reasonable to fight
    self.setBattleCost("7Eb", 10000)
    #3-6 Waters, too many...
    self.setBattleCost("49b", 3500)
    #1 zombull is actually not bad
    #self.setBattleCost("32a", 1200)
    #self.setBattleCost("32b", 1200)
    #Want to see WarMECH
    #self.setBattleCost("56a", -1000)
    #3 fires is too many
    self.setBattleCost("27b", 3500)
    #2 FrGiants, 2 FrWolves
    self.setBattleCost("2Eb", 1500)
    #2 Zombie Dragons - not hard, but mandatory damage taken (73)
    self.setBattleCost("4Bb", 1550)
    #1 worm - poison smoke/terminated
    self.setBattleCost("57a", 700)
    #3 worms - not sure if it's possible
    self.setBattleCost("57b", 4000)
    #2 gas dragons - not immune to BANE, but high magic resist
    #must take damage (73)
    self.setBattleCost("59b", 1800)

  def setBattleCost(self, group_name, cost):
    """set battle cost for a group by group name"""
    enemy_group_id = self.gdata.enemy_group_ids[group_name]
    self.battleCost[enemy_group_id] = cost

  def costFunctionIncremental(self, run_data, fight_list, last_fight):
    """
    Allows incremental cost function calculation. The run_data and
    fight_list might only represent part of a run. If the last fight
    of the previous part is provided, the cost for this section can
    be evaluated. For the first section, set last_fight == -1
    """
    #Evaluate normally
    cost = self.costFunctionByFightList(run_data, fight_list)
    #look at last fight and add in consecutive shadow factor if necessary
    if len(fight_list) == 0:
      return cost
    if last_fight == fight_list[0] and last_fight == self.shadow_id:
      cost += self.consecutiveShadowCost
    if fight_list[0] == self.zombied_id and last_fight in self.tofr_3f_list:
      cost += self.zombieDAfterPhantomCost
    return cost

  def costFunctionByFightList(self, run_data, fight_list):
    """Calculates a 'cost' from the run data and a list of fights"""
    cost = 0
    #first add up the cost of the steps and power cycles in each area
    for run_seg in run_data.run:
      if run_seg.steps > 0:
        cost += run_seg.steps * self.stepCost[run_seg.area_id]
      elif run_seg.isPowerCycle:
        cost += self.powerCycleCost
    #now add up the costs of all the battles
    for group_id in fight_list:
      cost += self.battleCost[group_id]

    #add in consecutive shadow cost to discourage two shadows in a row...
    last_group_id = -1
    for group_id in fight_list:
      if group_id == last_group_id and group_id == self.shadow_id:
        cost += self.consecutiveShadowCost
      if group_id == self.zombied_id and last_group_id in self.tofr_3f_list:
        cost += self.zombieDAfterPhantomCost
      last_group_id = group_id
    return cost

  def costFunctionFromResults(self, results):
    """Calculates a 'cost' from a resultSegments data structure"""
    cost = 0
    last_group_id = -1
    #go through list of results and add up the cost
    #for steps, fights and power cycles
    for result_seg in results.results:
      if result_seg.steps > 0:
        cost += result_seg.steps * self.stepCost[result_seg.area_id]
      elif result_seg.note == "power cycle":
        cost += self.powerCycleCost
      if result_seg.hasEncounter():
        group_id = result_seg.enemy_group_id
        cost += self.battleCost[group_id]
        #consecutive shadow cost...
        if group_id == last_group_id and group_id == self.shadow_id:
          cost += self.consecutiveShadowCost
        if group_id == self.zombied_id and last_group_id in self.tofr_3f_list:
          cost += self.zombieDAfterPhantomCost
        last_group_id = group_id
    return cost

  def costFunctionFromResultsVerbose(self, results, gdata):
    """Calculates a 'cost' from a resultSegments data structure"""
    cost = 0
    last_group_id = -1
    #go through list of results and add up the cost
    #for steps, fights and power cycles

    step_count = 0
    power_cycle_count = 0
    run_count = 0

    for result_seg in results.results:
      if result_seg.steps > 0:
        cost += result_seg.steps * self.stepCost[result_seg.area_id]
        step_count += result_seg.steps
      elif result_seg.note == "power cycle":
        cost += self.powerCycleCost
        power_cycle_count += 1
      if result_seg.hasEncounter():
        group_id = result_seg.enemy_group_id
        cost += self.battleCost[group_id]
        if self.battleCost[group_id] == self.runCost:
          run_count += 1
        else:
          group_name = gdata.enemy_groups[group_id]
          group_cost = self.battleCost[group_id]
          print "battle", str(group_id)+"/"+group_name, "cost",group_cost
        #consecutive shadow cost...
        if group_id == last_group_id and group_id == self.shadow_id:
          cost += self.consecutiveShadowCost
          print "consecutive shadows:", self.consecutiveShadowCost
        if group_id == self.zombied_id and last_group_id in self.tofr_3f_list:
          cost += self.zombieDAfterPhantomCost
          print "zombie d after phantom:", self.zombieDAfterPhantomCost
        last_group_id = group_id
    print step_count,"total steps"
    print power_cycle_count,"total power cycles"
    print run_count,"total runnable battles"
    return cost

